/*
    Geocache: some utilities for managing and visualizing geocache information
    Copyright (C) 2008  Gary Jackson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
 * Orthodrome.java
 */

package com.thegbomb.sphere;

/**
 *
 * @author Gary Jackson
 */
public class Orthodrome {
    private Point a, b, axis;
    private double length;
    
    /**
     * Creates a new instance of Orthodrome
     */
    public Orthodrome(Point a, Point b) {
        if (a.equals(b))
            throw new SphereException("null arc length");
        if (a.isAntipodal(b, 1E-15))
            throw new SphereException("points are antipodal, orthodrome is not unique");
        
        this.a = a;
        this.b = b;
        this.length = this.a.distance(b);
        this.axis = a.normal(b);
    }
    
    public double length() {
        return length;
    }
    
    public Point axis() {
        return axis;
    }
    
    public boolean intersects(Orthodrome o, double epsilon) {
        return intersects(o, epsilon, true);
    }
    
    private boolean overlaps(Orthodrome o, double epsilon) {
        return (Util.equals(a.distance(b), (a.distance(o.a) + b.distance(o.a)), epsilon) ||
                Util.equals(a.distance(b), (a.distance(o.b) + b.distance(o.b)), epsilon));
        
    }
    
    private boolean endpoints(Orthodrome o, double epsilon) {
        return Util.equals(a.distance(o.a), 0, epsilon) ||
                Util.equals(b.distance(o.a), 0, epsilon) ||
                Util.equals(a.distance(o.b), 0, epsilon) ||
                Util.equals(b.distance(o.b), 0, epsilon);
    }
    
    public boolean intersects(Orthodrome o, double epsilon, boolean inclusive) {
        if (this.equals(o))
            return true;
        
        boolean endpoints = endpoints(o, epsilon);
        if (inclusive && endpoints)
            return true;
        
        if (!inclusive && endpoints)
            return false;
        
        if (axis.equals(o.axis))
            return overlaps(o, epsilon);
        else {
            
            Point normal = axis.normal(o.axis);
            Point antipode = normal.antipode();
            
            // Return true if both intersect the normal vector
            if (Util.equals(normal.distance(a) + normal.distance(b), length, epsilon) &&
                    Util.equals(normal.distance(o.a) + normal.distance(o.b), o.length, epsilon)) {
                return true;
            }
            
            // Return true if both intersect the normal's antipodal vector
            if (Util.equals(antipode.distance(a) + antipode.distance(b), length, epsilon) &&
                    Util.equals(antipode.distance(o.a) + antipode.distance(o.b), o.length, epsilon)) {
                return true;
            }
            
            // Otherwise, return false
            return false;
        }
    }
    
    public Point getA() { return a; }
    public Point getB() { return b; }
    
    public String toString() {
        return "[" + a + " to " + b + "]";
    }
    
    public boolean equals(Orthodrome o) {
        return ((this.a == o.a) && (this.b == o.b)) ||
                ((this.a == o.b) && (this.b == o.a));
    }
    
    public int hashCode() {
        return this.toString().hashCode();
    }
}
